﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using System.Linq;
using AutoMapper;
using MovieDB.Common;
using MovieDB.DTOs;
using MovieDB.Entities;
using MovieDB.FactoryInterfaces;
using MovieDB.RepositoryInterfaces;
using MovieDB.ServiceInterfaces;

namespace MovieDB.Repositories
{
    public class MovieRepository :  IMovieRepository
    {
        private readonly IContext _movieContext;
        private readonly IMapping _mapping;
        private readonly IDtoFactory _dtoFactory;

        public MovieRepository(IContext movieContext,IMapping mapping,IDtoFactory dtoFactory )
        {
            _movieContext = movieContext;
            _mapping = mapping;
            _dtoFactory = dtoFactory;
        }

        private IContext CurrentContext { get { return _movieContext; } }

        #region IMovieRepository Members

        public IEnumerable<MovieDto> SearchForAutocomplete(string movieName)
        {
            var movieNameWithProperWildcards = string.Format("%{0}%", movieName.ToLower().Replace("*", "%"));
            var sqlParameter = new SqlParameter { ParameterName = "searchParameter", Value = movieNameWithProperWildcards };
            var movieEntities = CurrentContext.Movies.SqlQuery(@"SELECT * FROM Movies WHERE Lower(title) LIKE @searchParameter ORDER BY title",
                                                                              sqlParameter).ToList();

            var movies = Mapper.Map<IEnumerable<Movie>, IEnumerable<MovieDto>>(movieEntities);

            return movies;
        }

        public PagedMovieDto GetAllMoviesPaged(int page, int pageSize)
        {
            int totalAmountOfMovies = CurrentContext.Movies.Count();
            int skipPages = pageSize * page;

            List<Movie> movieEntities = GetMoviesWithAllChilds().OrderBy(movie => movie.Title).Skip(skipPages).Take(pageSize).ToList();
            var movies = Mapper.Map<List<Movie>, List<MovieDto>>(movieEntities);

            return _dtoFactory.CreatePagedMovieDto(movies, totalAmountOfMovies);
        }

        public MovieDto GetMovieById(Guid movieId)
        {
            var movie = GetMoviesWithAllChilds().Where(mov => mov.Id == movieId).Single();

            return Mapper.Map<Movie, MovieDto>(movie);
        }

        public PagedMovieDto GetMoviesByIds(IEnumerable<Guid> movieIds, int page, int pageSize)
        {
            int totalAmountOfMovies = CurrentContext.Movies.Where(movie => movieIds.Contains(movie.Id)).Count();
            int skipPages = pageSize * page;

            var movieEntites = GetMoviesWithAllChilds().Where(movie => movieIds.Contains(movie.Id)).OrderBy(movie => movie.Title)
                .Skip(skipPages).Take(pageSize).ToList();

            var movies = Mapper.Map<List<Movie>, List<MovieDto>>(movieEntites);

            return _dtoFactory.CreatePagedMovieDto(movies, totalAmountOfMovies);
        }

        public PagedMovieDto GetMovieByGenre(MovieGenre category, int page, int pageSize)
        {
            int totalAmountOfMovies = CurrentContext.Movies.Where(movie => movie.Genre == (int) category).Count();
            int skipPages = pageSize * page;

            var movieEntites = GetMoviesWithAllChilds().Where(movie => movie.Genre == (int) category).OrderBy(movie => movie.Title)
                .Skip(skipPages).Take(pageSize).ToList();

            var movies = Mapper.Map<List<Movie>, List<MovieDto>>(movieEntites);

            return _dtoFactory.CreatePagedMovieDto(movies, totalAmountOfMovies);
        }

        public PagedMovieDto GetMovieByVideoType(VideoType videoType, int page, int pageSize)
        {
            int totalAmountOfMovies = CurrentContext.Movies.Where(movie => movie.VideoQuality.VideoType == (int)videoType).Count();
            int skipPages = pageSize * page;

            var movieEntites = GetMoviesWithAllChilds().
                Where(movie => movie.VideoQuality.VideoType == (int)videoType).OrderBy(movie => movie.VideoQuality.VideoResolution)
                .ThenByDescending(movie => movie.AudioQualities.FirstOrDefault().AudioType)
                .ThenByDescending(movie => movie.AudioQualities.FirstOrDefault().AudioChannels)
                .ThenBy(movie => movie.Title)
                .Skip(skipPages).Take(pageSize).ToList();

            var movies = Mapper.Map<List<Movie>, List<MovieDto>>(movieEntites);

            return _dtoFactory.CreatePagedMovieDto(movies, totalAmountOfMovies);
        }

        public IEnumerable<MovieDto> GetMostRecentMovies(int amountOfRecentMovies)
        {
            if (amountOfRecentMovies <= 0)
            {
                throw new ArgumentOutOfRangeException("amountOfRecentMovies", "Only positive digits are allowed.");
            }

            IQueryable<Movie> movieEntities = GetMoviesWithAllChilds().OrderByDescending(movie => movie.CreatedAt).ThenBy(movie => movie.Title)
                .ThenBy(movie => movie.Genre)
                .Take(amountOfRecentMovies);


            return _mapping.MapFromSourceToDestination<IEnumerable<Movie>, IEnumerable<MovieDto>>(movieEntities);
        }

        public void SaveMovie(MovieDto movieDto)
        {
            if (movieDto == null)
            {
                throw new ArgumentNullException("movieDto");
            }

            Movie movieFromDb = GetMoviesWithAllChilds().SingleOrDefault(mov => mov.Id == movieDto.Id);
           
            //note create
            if (movieFromDb == null)
            {
                movieFromDb = _mapping.MapFromSourceToDestination<MovieDto, Movie>(movieDto);
            }
            var matchingAudioQualities = GetMatchingAudioQualities(movieDto.AudioQualities);

            _mapping.MapFromSourceToDestination(movieDto, movieFromDb);
            movieFromDb.VideoQuality = GetMatchingVideoQuality(movieDto.VideoQuality.VideoType, movieDto.VideoQuality.VideoResolution);
            AddMatchingAudioQualitiesToMovieEntity(movieFromDb, matchingAudioQualities);

            CurrentContext.SaveChanges();
        }

        public void DeleteMovie(Guid movieId)
        {
            var movieToDelete = CurrentContext.Movies.Single(movie => movie.Id == movieId);

            var audioQualitesFromMappingTable = CurrentContext.AudioQualities.ToList();
            foreach (var audioQuality in audioQualitesFromMappingTable)
            {
                if (audioQuality.Movies != null)
                {
                    audioQuality.Movies.Remove(movieToDelete);
                }
            }
            CurrentContext.Movies.Remove(movieToDelete);

            CurrentContext.SaveChanges();
        }

        public bool IsTitleAvailable(string title, Guid? movieId = null)
        {
            if (title == null)
            {
                throw new ArgumentNullException("title");
            }

            if (movieId.HasValue)
            {
                return !CurrentContext.Movies.Any(movie => movie.Id != movieId && movie.Title.ToLower() == title.ToLower());
            }
            return !CurrentContext.Movies.Any(movie => movie.Title.ToLower() == title.ToLower());
        }

        public AudioQualityDto GetAudioQualityDto(AudioType audioType, AudioChannels audioChannels)
        {
            AudioQuality audioQualityEntity = GetMatchingAudioQuality(audioType, audioChannels);
            return _mapping.MapFromSourceToDestination<AudioQuality, AudioQualityDto>(audioQualityEntity);
        }

        public AudioQuality GetAudioQualityEntity(AudioType audioType, AudioChannels audioChannels)
        {
            return GetMatchingAudioQuality(audioType, audioChannels);
        }

        public VideoQualityDto GetVideoQualityDto(VideoType videoType, VideoResolution videoResolution)
        {
            VideoQuality videoQualityEntity = GetMatchingVideoQuality(videoType, videoResolution);
            return _mapping.MapFromSourceToDestination<VideoQuality, VideoQualityDto>(videoQualityEntity);
        }

        public VideoQuality GetVideoQualityEntity(VideoType videoType, VideoResolution videoResolution)
        {
            return GetMatchingVideoQuality(videoType, videoResolution);
        }

        #endregion

        private void AddMatchingAudioQualitiesToMovieEntity(Movie movieFromDb, IEnumerable<AudioQuality> matchingAudioQualities)
        {
            movieFromDb.AudioQualities.Clear();

            foreach (AudioQuality matchingAudioQuality in matchingAudioQualities)
            {
                if (!matchingAudioQuality.Movies.Contains(movieFromDb))
                {
                    matchingAudioQuality.Movies.Add(movieFromDb);
                }

                movieFromDb.AudioQualities.Add(matchingAudioQuality);
            }
        }

        private VideoQuality GetMatchingVideoQuality(VideoType videoType, VideoResolution videoResolution)
        {
            return CurrentContext.VideoQualities.Where(videoQuality => videoQuality.VideoType == (int) videoType && videoQuality.VideoResolution == (int) videoResolution).Single();
        }

        private AudioQuality GetMatchingAudioQuality(AudioType audioType, AudioChannels audioChannels)
        {
            return CurrentContext.AudioQualities.Include("Movies").Where(audioQuality => audioQuality.AudioType == (int) audioType && audioQuality.AudioChannels == (int) audioChannels).Single();
        }

        private DbQuery<Movie> GetMoviesWithAllChilds()
        {
            return CurrentContext.Movies.Include("VideoQuality").Include("AudioQualities");
        }

        private ICollection<AudioQuality> GetMatchingAudioQualities(IEnumerable<AudioQualityDto> audioQualities)
        {
            return audioQualities.Select(audioQuality => GetMatchingAudioQuality(audioQuality.AudioType, audioQuality.AudioChannels)).ToList();
        }
    }
}